import * as THREE from "three"
import SnapLine3 from "./SnapLine3"
import SnapVector3 from "./SnapVector3"
interface item {
  distance: number
  object: SnapLine3 | SnapVector3
  distanceToRay: number
  point: THREE.Vector3 | SnapVector3
  isLineOctreeIntersection: boolean
}
class LineOctreeNode {
  raycast: (i: THREE.Raycaster, n: number, s: item[]) => void
  children: (LineOctreeNode | SnapLine3 | SnapVector3)[]
  level: number
  childNodes: LineOctreeNode[]
  bounds: THREE.Box3
  constructor(e: THREE.Box3, t: number) {
    this.raycast = (() => {
      const e = new THREE.Vector3()
      const t = new THREE.Box3()
      return (i: THREE.Raycaster, n: number, s: item[]) => {
        const r = t.copy(this.bounds).expandByScalar(n)
        if (i.ray.intersectsBox(r))
          for (const t of this.children)
            if (t instanceof LineOctreeNode) t.raycast(i, n, s)
            else if (t instanceof SnapLine3) {
              const r = e
              const o = i.ray.distanceSqToSegment(t.start, t.end, void 0, e)
              const a = Math.sqrt(o)
              a < n &&
                s.push({
                  distance: i.ray.origin.distanceTo(r),
                  object: t,
                  distanceToRay: a,
                  point: r.clone(),
                  isLineOctreeIntersection: !0
                })
            } else if (t instanceof SnapVector3) {
              const e = i.ray.distanceToPoint(t)
              e < n &&
                s.push({
                  distance: i.ray.origin.distanceTo(t),
                  object: t,
                  distanceToRay: e,
                  point: t.clone(),
                  isLineOctreeIntersection: !0
                })
            }
      }
    })()
    this.level = t
    this.bounds = e.clone()
    this.childNodes = []
    this.children = []
  }

  add(e: SnapLine3 | SnapVector3) {
    let t = !1
    const i = e instanceof THREE.Line3 ? [e.start, e.end] : [e]
    if (this.level < 4) {
      !this.childNodes.length && this.buildChildNodes()
      for (const n of this.childNodes) i.every(e => n.bounds.containsPoint(e)) && ((t = !0), n.add(e))
    }
    t || this.children.push(e)
  }
  remove(e: SnapLine3 | SnapVector3) {
    const t = e instanceof THREE.Line3 ? [e.start, e.end] : [e]
    for (let i = 0; i < this.children.length; i++) {
      const n = this.children[i]
      if (n instanceof LineOctreeNode) {
        if (t.every(e => n.bounds.containsPoint(e)) && n.remove(e)) return !0
      } else if (n === e) return this.children.splice(i, 1), !0
    }
    return !1
  }
  buildChildNodes() {
    const e = this.bounds.max.clone().sub(this.bounds.min).clone().multiplyScalar(0.5)
    for (let t = 0; t < 2; t++)
      for (let i = 0; i < 2; i++)
        for (let s = 0; s < 2; s++) {
          const r = this.bounds.min.x + e.x * s,
            o = r + e.x,
            a = this.bounds.min.y + e.y * t,
            l = a + e.y,
            c = this.bounds.min.z + e.z * i,
            h = c + e.z,
            d = new THREE.Box3(new THREE.Vector3(r, a, c), new THREE.Vector3(o, l, h)),
            u = new LineOctreeNode(d, this.level + 1)
          this.childNodes.push(u)
          this.children.push(u)
        }
  }
  spherecast(e, t: SnapVector3[]) {
    if (e.intersectsBox(this.bounds))
      for (const i of this.children)
        if (i instanceof LineOctreeNode) i.spherecast(e, t)
        else if (i instanceof SnapLine3) {
          const s = i.closestPointToPoint(e.center, !0, new THREE.Vector3())
          s.distanceTo(e.center) < e.radius && t.push(new SnapVector3(s))
        } else i instanceof THREE.Vector3 && i.distanceTo(e.center) < e.radius && t.push(i)
  }
  traverse(e) {
    for (const t of this.children) t instanceof LineOctreeNode ? t.traverse(e) : e(t)
  }
}
export default class LineOctree {
  pointCount: number
  lineCount: number
  root: LineOctreeNode
  constructor(e: THREE.Box3) {
    this.pointCount = 0
    this.lineCount = 0
    this.root = new LineOctreeNode(e, 0)
  }
  add(e: SnapLine3 | SnapVector3) {
    e instanceof THREE.Line3 ? this.lineCount++ : e instanceof THREE.Vector3 && this.pointCount++
    this.root.add(e)
  }
  remove(e: SnapLine3 | SnapVector3) {
    e instanceof SnapLine3 ? this.lineCount-- : e instanceof SnapVector3 && this.pointCount--
    this.root.remove(e)
  }
  traverse(e) {
    this.root.traverse(e)
  }
  raycast(e: THREE.Raycaster, t: number) {
    const i: item[] = []
    this.root.raycast(e, t, i)
    return i
  }
}
